﻿using System;
using System.Collections.Generic;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Xml;
using EnvDTE;
using EnvDTE80;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.VersionControl.Client;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.CommandBars;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TeamFoundation;
using VsExt.WorkItemVisualizer.Colors;

namespace VsExt.WorkItemVisualizer
{
    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidVsExt_WorkItemVisualizerPkgString)]
    [ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.NoSolution)]
    public sealed class VsExt_WorkItemVisualizerPackage : Package, IVsShellPropertyEvents
    {
        #region private members
        private DTE2 dte2;
        private WorkItemStore _wis;
        private VersionControlServer _vcs;
        private Dictionary<int, WorkItemStub> _workItemStubs;
        private List<string> _groupByDistinctValues = new List<string>();
        private List<string> _allNodeTypes;
        private List<TempLinkType> _selectedLinks;
        private List<TempLinkType> lstLinkTypes;
        private List<TempLinkType2> _links;
        private uint _EventSinkCookie;
        private string currentMRUComboChoice = null;
        private string _projectName;
        private string outputFile;
        private const string TeamFoundationServerExtService = "Microsoft.VisualStudio.TeamFoundation.TeamFoundationServerExt";
        int id = 0;
        #endregion

        public VsExt_WorkItemVisualizerPackage()
        {
        }

        protected override void Initialize()
        {
            base.Initialize();

            dte2 = (DTE2)GetService(typeof(DTE));
            outputFile = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Output.dgml";

            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if (null != mcs)
            {
                CommandID menuMyMRUComboCommandID = new CommandID(GuidList.guidVsExt_WorkItemVisualizerCmdSet, (int)PkgCmdIDList.cmdidWorkItemVisualizerCombo);
                OleMenuCommand menuMyMRUComboCommand = new OleMenuCommand(new EventHandler(OnMenuMyMRUCombo), menuMyMRUComboCommandID);
                menuMyMRUComboCommand.ParametersDescription = "$";
                mcs.AddCommand(menuMyMRUComboCommand);
            }

            var shellService = GetService(typeof(SVsShell)) as IVsShell;
            if (shellService != null)
            {
                ErrorHandler.ThrowOnFailure(shellService.
                AdviseShellPropertyChanges(this, out _EventSinkCookie));
            }
        }

        private void OnMenuMyMRUCombo(object sender, EventArgs e)
        {
            if (e == EventArgs.Empty)
            {
                throw (new ArgumentException(Resources.EventArgsRequired));
            }

            OleMenuCmdEventArgs eventArgs = e as OleMenuCmdEventArgs;

            if (eventArgs != null)
            {
                object input = eventArgs.InValue;
                IntPtr vOut = eventArgs.OutValue;

                if (vOut != IntPtr.Zero && input != null)
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal));
                }
                else if (vOut != IntPtr.Zero)
                {
                    Marshal.GetNativeVariantForObject(this.currentMRUComboChoice, vOut);
                }

                else if (input != null)
                {
                    string newChoice = input.ToString();
                    if (!string.IsNullOrEmpty(newChoice))
                    {
                        this.currentMRUComboChoice = newChoice;
                        RunSearchCommand(this.currentMRUComboChoice);
                    }
                    else
                    {
                        throw (new ArgumentException(Resources.EmptyStringIllegal));
                    }
                }
                else
                {
                    throw (new ArgumentException(Resources.BothInOutParamsIllegal));
                }
            }
            else
            {
                throw (new ArgumentException(Resources.EventArgsRequired));
            }
        }

        private TeamFoundationServerExt GetTFSExt()
        {
            TeamFoundationServerExt tfsExt = this.dte2.GetObject(TeamFoundationServerExtService) as TeamFoundationServerExt;

            if (tfsExt.ActiveProjectContext.DomainUri == null || tfsExt.ActiveProjectContext.ProjectUri == null)
            {
                MessageBox.Show(Resources.ErrorNotConnected, Resources.ToolbarName, MessageBoxButtons.OK, MessageBoxIcon.Information);
                return null;
            }
            else
            {
                return tfsExt;
            }
        }

        public int OnShellPropertyChange(int propid, object propValue)
        {
            if ((int)__VSSPROPID.VSSPROPID_Zombie == propid)
            {
                if ((bool)propValue == false)
                {
                    var cbs = ((CommandBars)dte2.CommandBars);
                    CommandBar cb = cbs["Work Item Visualizer"];
                    cb.Visible = true;

                    var shellService = GetService(typeof(SVsShell)) as IVsShell;
                    if (shellService != null)
                    {
                        ErrorHandler.ThrowOnFailure(shellService.
                        UnadviseShellPropertyChanges(_EventSinkCookie));
                    }

                    _EventSinkCookie = 0;
                }
            }

            return VSConstants.S_OK;
        }

        private void RunSearchCommand(string input)
        {
            TeamFoundationServerExt tfsExt = this.GetTFSExt();
            if (tfsExt == null)
            {
                return;
            }

            TfsTeamProjectCollection activeTFS = new TfsTeamProjectCollection(new Uri(tfsExt.ActiveProjectContext.DomainUri));
            _wis = (WorkItemStore)activeTFS.GetService(typeof(WorkItemStore));
            _vcs = (VersionControlServer)activeTFS.GetService(typeof(VersionControlServer));


            int.TryParse(input, out id);

            WorkItem workItem = _wis.GetWorkItem(id);
            _projectName = workItem.Fields["Team Project"].Value.ToString();

            LoadLinkTypes();
            Process(workItem);
        }

        private void Process(WorkItem workItem)
        {
            _workItemStubs = new Dictionary<int, WorkItemStub>();
            _allNodeTypes = new List<string>();
            _links = LoadLinkTypes(_projectName);

            dte2.StatusBar.Text = "Work Item Visualizer - Processing Work Items";
            ProcessWorkItemCS(workItem);
            dte2.StatusBar.Text = "Work Item Visualizer - Writing Output";
            WriteChangesetInfo(_projectName);
        }

        private List<TempLinkType2> LoadLinkTypes(string project)
        {
            List<TempLinkType2> links = new List<TempLinkType2>();
            WorkItemLinkTypeCollection linkTypes = _wis.Projects[project].Store.WorkItemLinkTypes;

            foreach (WorkItemLinkType t in linkTypes)
            {
                links.Add(new TempLinkType2() { Reverse = t.ForwardEnd.Name, Forward = t.ReverseEnd.Name });
            }

            links.Add(new TempLinkType2() { Forward = "Associated Work Items", Reverse = "Has Files" });
            return links;
        }

        private void ProcessWorkItemCS(WorkItem wi)
        {
            WorkItemStub s = CheckStub(wi);

            for (int i = 0; i < wi.WorkItemLinks.Count; i++)
            {
                if (id == wi.Id)
                    dte2.StatusBar.Text = "Work Item Visualizer - Reading Parent Links: " + i.ToString() + " out of " + wi.WorkItemLinks.Count.ToString();
                else
                    dte2.StatusBar.Text = "Work Item Visualizer - Reading Child Links: " + i.ToString() + " out of " + wi.WorkItemLinks.Count.ToString();

                if (IsLinkSelected(wi.WorkItemLinks[i].LinkTypeEnd.Name))
                {
                    ProcessLinks(wi, wi.WorkItemLinks[i], s);
                }
            }
        }

        private void GetWorkItemParent(WorkItemStub stub)
        {
            WorkItemStub s = null;

            List<WorkItemStub> r = GetRelatedWorkItems(stub);
            for (int i = 0; i < r.Count; i++)
            {
                s = GetStub(r[i].ID);
                GetWorkItemParent(s);
            }
        }

        private void WriteChangesetInfo(string project)
        {
            IColorConfiguration colorConfiguration;
            XmlTextWriter xtw = XmlHelper.CreateHeader(outputFile);

            #region Write Nodes
            dte2.StatusBar.Text = "Work Item Visualizer - Generating Nodes";
            xtw.WriteStartElement("Nodes");

            foreach (WorkItemStub workItemStub in _workItemStubs.Values)
            {
                List<string> categories = new List<string>();

                for (int l = 0; l < workItemStub.Related.Count; l++)
                {
                    if (!categories.Contains(workItemStub.Related[l].Relationship.ToString()))
                        categories.Add(workItemStub.Related[l].Relationship.ToString());
                }

                XmlHelper.WriteXMLNode(true, true, xtw, workItemStub.ID.ToString(), workItemStub.ID.ToString() + ": " + workItemStub.Title,
                    workItemStub.WorkItemTypeName, categories.ToArray(), "WorkItem", workItemStub.ID.ToString());
            }
            xtw.WriteEndElement();
            #endregion

            dte2.StatusBar.Text = "Work Item Visualizer - Generating Links";
            #region Write Links
            xtw.WriteStartElement("Links");

            foreach (WorkItemStub workItemStub in _workItemStubs.Values)
            {
                for (int j = 0; j < workItemStub.Related.Count; j++)
                {
                    XmlHelper.WriteXMLLink(xtw, workItemStub.ID.ToString(), workItemStub.Related[j].ID.ToString(), workItemStub.Related[j].Relationship.ToString(), string.Empty);
                }
            }
            xtw.WriteEndElement();
            #endregion

            #region Write Categories
            dte2.StatusBar.Text = "Work Item Visualizer - Generating Categories";
            xtw.WriteStartElement("Categories");
            for (int i = 0; i < _links.Count; i++)
            {
                if (_links[i].Forward != "Related")
                    XmlHelper.WriteNavigationCategory(xtw, _links[i].ToString(), _links[i].Forward, _links[i].Reverse, "True");
                else
                    XmlHelper.WriteNavigationCategory(xtw, _links[i].ToString(), _links[i].Forward, _links[i].Reverse, "False");
            }
            xtw.WriteEndElement();
            #endregion

            #region Write Properties
            dte2.StatusBar.Text = "Work Item Visualizer - Generating Properties";
            xtw.WriteStartElement("Properties");
            XmlHelper.WriteXMLReference(xtw, "WorkItem", "True", UrlHelper.GetTWAWorkItemBaseUrl(_wis));
            xtw.WriteEndElement();
            #endregion

            #region Write Styles
            dte2.StatusBar.Text = "Work Item Visualizer - Generating Styles";
            xtw.WriteStartElement("Styles");
            colorConfiguration = new CyclicColorConfiguration("state");

            for (int i = 0; i < _allNodeTypes.Count; i++)
            {
                string label;

                label = _allNodeTypes[i];

                XmlHelper.WriteXMLNodeStyle(xtw, label, colorConfiguration.GetColor(label));
            }

            xtw.WriteEndElement();
            #endregion

            xtw.WriteEndElement();
            xtw.Close();

            try
            {
                if (dte2.Documents.Count > 0)
                {
                    Document doc = dte2.Documents.Item(outputFile);
                    Window win = doc.ActiveWindow;
                    win.Activate();
                    win.Close(vsSaveChanges.vsSaveChangesNo);
                }
            }
            catch (Exception ex)
            {

            }
            dte2.StatusBar.Text = "Work Item Visualizer - Opening File";
            dte2.ItemOperations.OpenFile(outputFile);
            dte2.StatusBar.Text = "Work Item Visualizer - Finished";
        }

        private WorkItemStub CheckStub(WorkItem wi)
        {
            WorkItemStub s = GetStub(wi.Id);

            if (s == null)
            {
                s = new WorkItemStub();
                s.ID = wi.Id;
                s.Title = wi.Fields[CoreField.Title].Value.ToString();
                s.WorkItemTypeName = wi.Fields[CoreField.WorkItemType].Value.ToString();

                if (!_allNodeTypes.Contains(s.WorkItemTypeName))
                    _allNodeTypes.Add(s.WorkItemTypeName);

                _workItemStubs.Add(s.ID, s);
            }

            return s;
        }

        private WorkItemStub GetStub(int id)
        {
            WorkItemStub s;
            _workItemStubs.TryGetValue(id, out s);
            return s;
        }

        private bool IsLinkSelected(string link)
        {
            return GetLinkType(link).Selected;
        }

        private TempLinkType GetLinkType(string link)
        {
            TempLinkType result = _selectedLinks.Find(
                delegate(TempLinkType s)
                {
                    return s.LinkType == link;
                }
            );

            return result;
        }

        private void ProcessLinks(WorkItem wi, WorkItemLink wiLink, WorkItemStub s)
        {
            int targetID = wiLink.TargetId;
            WorkItemStub result;

            _workItemStubs.TryGetValue(targetID, out result);

            if (result == null)
                ProcessWorkItemCS(_wis.GetWorkItem(targetID));

            WorkItemRelationship result1 = s.Related.Find(
                delegate(WorkItemRelationship stub)
                {
                    return stub.ToString() == targetID.ToString() + wiLink.LinkTypeEnd.Name;
                }
            );

            if (result1 == null)
            {
                WorkItemStub workItemStub;
                bool found = false;

                if (_workItemStubs.TryGetValue(targetID, out workItemStub))
                {
                    for (int k = 0; k < workItemStub.Related.Count; k++)
                    {
                        if (workItemStub.Related[k].ID == wi.Id)
                        {
                            found = true;
                            break;
                        }
                    }
                }

                if (!found)
                {
                    TempLinkType2 t = GetRelationship(wiLink.LinkTypeEnd.Name);
                    if (t.Reverse == wiLink.LinkTypeEnd.Name)
                    {
                        s.Related.Add(new WorkItemRelationship() { ID = targetID, End = wiLink.LinkTypeEnd.Name, Relationship = GetRelationship(wiLink.LinkTypeEnd.Name) });
                    }
                }
            }
        }

        private List<WorkItemStub> GetRelatedWorkItems(WorkItemStub stub)
        {
            List<WorkItemStub> related = new List<WorkItemStub>();

            for (int i = 0; i < _workItemStubs.Count; i++)
            {
                for (int k = 0; k < _workItemStubs[i].Related.Count; k++)
                {
                    if ((_workItemStubs[i].Related[k].ID == stub.ID) && (_workItemStubs[i].Related[k].End == "Child"))
                    {
                        related.Add(_workItemStubs[i]);
                    }
                }
            }
            return related;
        }

        private TempLinkType2 GetRelationship(string value)
        {
            TempLinkType2 s = _links.Find(
                delegate(TempLinkType2 stub)
                {
                    return stub.Reverse == value;
                }
            );

            if (s == null)
            {
                s = _links.Find(
                    delegate(TempLinkType2 stub)
                    {
                        return stub.Forward == value;
                    }
                );
            }

            return s;
        }

        private void LoadLinkTypes()
        {
            WorkItemLinkTypeCollection linkTypes = _wis.Projects[_projectName].Store.WorkItemLinkTypes;

            _selectedLinks = new List<TempLinkType>();

            foreach (WorkItemLinkType t in linkTypes)
            {
                _selectedLinks.Add(new TempLinkType() { LinkType = t.ForwardEnd.Name, Selected = true, Enabled = true, IsForward = true });
                if (t.ForwardEnd.Name != "Related")
                    _selectedLinks.Add(new TempLinkType() { LinkType = t.ReverseEnd.Name, Selected = true, Enabled = true, IsForward = false });
            }

            lstLinkTypes = _selectedLinks;
        }
    }
}
